package com.fingard.dsp.bank.directbank.citic05.util;

import com.citicbank.cbframework.common.exception.CBException;
import com.citicbank.cbframework.common.security.CBRSA;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public class CiticSdk implements CiticSdkInterface {
    private static CiticSdkInterface instance;
    private PrivateKey privateKeyDecrypt;
    private PrivateKey privateKeySignature;
    private PublicKey publicKeyEncrypt;
    private PublicKey publicKeyValidateSignature;

    public CiticSdk() {
        this.configuration = new OpenBankSdkConfiguration();
    }

    public static CiticSdkInterface getInstance() {
        return new CiticSdk();
    }

    public static String getProxyIP() {
        return proxyIP;
    }

    public static void setProxyIP(String proxyIP) {
        proxyIP = proxyIP;
    }

    public static int getProxyPort() {
        return proxyPort;
    }

    public static void setProxyPort(int proxyPort) {
        proxyPort = proxyPort;
    }

    public static String getProxyType() {
        return proxyType;
    }

    public static void setProxyType(String proxyType) {
        proxyType = proxyType;
    }

    private static String proxyType = "https";
    private static String proxyIP;
    private static int proxyPort;
    private static String ENCODING = "utf8";
    private OpenBankSdkConfiguration configuration;

    private void checkConfiguration() {
        if ((this.configuration.getOpenBusiType() == null) || ("".equals(this.configuration.getOpenBusiType()))) {
            throw new IllegalArgumentException("æªè®¾ç½® open_busi_type åé");
        }
        if ((this.configuration.getOpenVer() == null) || ("".equals(this.configuration.getOpenVer()))) {
            throw new IllegalArgumentException("æªè®¾ç½® open_ver åé");
        }
        if ((this.configuration.getOpenMerCode() == null) || ("".equals(this.configuration.getOpenMerCode()))) {
            throw new IllegalArgumentException("æªè®¾ç½® open_mer_code åé");
        }
        if ((this.configuration.getOpenMerName() == null) || ("".equals(this.configuration.getOpenMerName()))) {
            throw new IllegalArgumentException("æªè®¾ç½® open_mer_name åé");
        }
        if ((this.configuration.getPublicUrl() == null) || ("".equals(this.configuration.getPublicUrl()))) {
            throw new IllegalArgumentException("æªè®¾ç½® public_url åé");
        }
        if ((this.configuration.getTransferEncode() == null) || ("".equals(this.configuration.getTransferEncode()))) {
            throw new IllegalArgumentException("æªè®¾ç½® transfer_encode åé");
        }
        if (this.configuration.getConnectionTimeout() == -1) {
            throw new IllegalArgumentException("æªè®¾ç½® open_mer_name åé");
        }
    }

    public PrivateKey getPrivateKeyDecrypt() {
        return this.privateKeyDecrypt;
    }

    public void setOpenVer(String openVer) {
        this.configuration.setOpenVer(openVer);
    }

    public void setOpenMerCode(String openMerCode) {
        this.configuration.setOpenMerCode(openMerCode);
    }

    public void setOpenMerName(String openMerName) {
        this.configuration.setOpenMerName(openMerName);
    }

    public void setOpenBusiType(String openBusiType) {
        this.configuration.setOpenBusiType(openBusiType);
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.configuration.setConnectionTimeout(connectionTimeout);
    }

    public void setPublicUrl(String publicUrl) {
        this.configuration.setPublicUrl(publicUrl);
    }

    public void setTransferEncode(String code) {
        this.configuration.setTransferEncode(code);
    }

    private PrivateKey generatePrivKeyFromBytes(byte[] enc_key, byte[] pwd) throws Exception {
        char[] keyPassword = null;
        try {
            keyPassword = new String(pwd, "UTF-8").toCharArray();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        PrivateKey privateKey = null;
        try {
            privateKey = CryptUtil.decryptPrivateKey(enc_key, keyPassword);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    public void setPrivateKeyDecryptWithPwd(byte[] enc_key, byte[] pwd) throws Exception {
        PrivateKey key = generatePrivKeyFromBytes(enc_key, pwd);
        if (key == null) {
            logger.error("è®¾ç½® privateKeyDecrypt å¤±è´¥, æå·¥ä¿®æ¹å¯é¥æä»¶å¯è½å¯¼è´è¿ç§å¤±è´¥");
            throw new RuntimeException("è®¾ç½® privateKeyDecrypt å¤±è´¥");
        }
        this.privateKeyDecrypt = key;
    }

    public PrivateKey getPrivateKeySignature() {
        return this.privateKeySignature;
    }

    public void setPrivateKeySignatureWithPwd(byte[] enc_key, byte[] pwd) throws Exception {
        PrivateKey key = generatePrivKeyFromBytes(enc_key, pwd);
        if (key == null) {
            logger.error("è®¾ç½® privateKeySignature å¤±è´¥, æå·¥ä¿®æ¹å¯é¥æä»¶å¯è½å¯¼è´è¿ç§å¤±è´¥");
            throw new RuntimeException("è®¾ç½® privateKeySignature å¤±è´¥");
        }
        this.privateKeySignature = key;
    }

    public void setPrivateKeyDecryptWithPwdUseStr(String base64KeyStr, String pwdStr) throws Exception {
        try {
            setPrivateKeyDecryptWithPwd(Base64.decode(base64KeyStr.getBytes("UTF-8")), pwdStr.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public void setPrivateKeySignatureWithPwdUseStr(String base64KeyStr, String pwdStr) throws Exception {
        try {
            setPrivateKeySignatureWithPwd(Base64.decode(base64KeyStr.getBytes("UTF-8")), pwdStr.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public PublicKey getPublicKeyEncrypt() {
        return this.publicKeyEncrypt;
    }

    public void setPublicKeyEncrypt(byte[] key) {
        PublicKey pubKey = generatePubKeyFromBytes(key);
        if (pubKey == null) {
            logger.error("è®¾ç½® publicKeyEncrypt å¤±è´¥, æå·¥ä¿®æ¹å¯é¥æä»¶å¯è½å¯¼è´è¿ç§å¤±è´¥");
            throw new RuntimeException("è®¾ç½® publicKeyEncrypt å¤±è´¥");
        }
        this.publicKeyEncrypt = pubKey;
    }

    public PublicKey getPublicKeyValidateSignature() {
        return this.publicKeyValidateSignature;
    }

    public void setPublicKeyValidateSignature(byte[] key) {
        PublicKey pubKey = generatePubKeyFromBytes(key);
        if (pubKey == null) {
            logger.error("è®¾ç½® publicKeyValidateSignature å¤±è´¥, æå·¥ä¿®æ¹å¯é¥æä»¶å¯è½å¯¼è´è¿ç§å¤±è´¥");
            throw new RuntimeException("è®¾ç½® publicKeyValidateSignature å¤±è´¥");
        }
        this.publicKeyValidateSignature = pubKey;
    }

    public void setPublicKeyEncryptUseStr(String base64Key) {
        try {
            setPublicKeyEncrypt(Base64.decode(base64Key.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public void setPublicKeyValidateSignatureUseStr(String base64Key) {
        try {
            setPublicKeyValidateSignature(Base64.decode(base64Key.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private PublicKey generatePubKeyFromBytes(byte[] key) {
        X509Certificate cert = null;
        try {
            cert = CryptUtil.generateX509Certificate(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cert.getPublicKey();
    }

    private static Logger logger = LoggerFactory.getLogger(CiticSdk.class);
    private static HttpClient httpClient = null;

    protected static HttpClient getHttpClient()
            throws KeyManagementException, NoSuchAlgorithmException {
        if (httpClient != null) {
            return httpClient;
        }
        SSLContext sslContext = SSLContexts.custom().build();
        TrustManager trustManager = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
            }

            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        sslContext.init(null, new TrustManager[]{trustManager}, null);

        Registry<Object> socketFactoryRegistry = RegistryBuilder.create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)).build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        if ((null != proxyIP) && (!"".equals(proxyIP)) && (proxyPort > 0)) {
            HttpHost porxy = new HttpHost(proxyIP, proxyPort, proxyType);
            DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(porxy);
            httpClient = HttpClients.custom().setRoutePlanner(routePlanner).setConnectionManager(connectionManager).build();
        } else {
            httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
        }
        return httpClient;
    }

    protected String buildReqJson(RequestHeader header, String reqBody)
            throws Exception {
        if ((header.getOpenMerFlowId() == null) || ("".equals(header.getOpenMerFlowId()))) {
            throw new IllegalArgumentException("åæ·æµæ°´å·ä¸å¾ä¸ºç©º");
        }
        if ((header.getOpenTransCode() == null) || ("".equals(header.getOpenTransCode()))) {
            throw new IllegalArgumentException("äº¤æç ä¸å¾ä¸ºç©º");
        }
        String reqFormat = "{\n \"OPENVER\": \"%s\",\n \"OPENTRANSCODE\": \"%s\",\n \"OPENMERCODE\": \"%s\",\n \"OPENMERNAME\": \"%s\",\n \"OPENBUSITYPE\": \"%s\",\n \"OPENLAUNCHDATE\": \"%s\",\n \"OPENLAUNCHTIME\": \"%s\",\n \"OPENMERFLOWID\": \"%s\",\n \"txCode\": \"%s\",\n %s\n}\n";
        if ((reqBody == null) || ("".equals(reqBody.trim())) || ("{}".equals(reqBody.trim()))) {
            reqBody = "{\"__blank_body__\": \"\"}";
        }
        reqBody = reqBody.substring(reqBody.indexOf('{') + 1, reqBody.lastIndexOf('}'));

        DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
        DateFormat HHmmss = new SimpleDateFormat("HHmmss");

        String reqJson = String.format(reqFormat, new Object[]{header.getOpenVer() == null ? this.configuration.getOpenVer() : header.getOpenVer(), header.getOpenTransCode(), header.getOpenMerCode() == null ? this.configuration.getOpenMerCode() : header.getOpenMerCode(), header.getOpenMerName() == null ? this.configuration.getOpenMerName() : header.getOpenMerName(), header.getOpenBusiType() == null ? this.configuration.getOpenBusiType() : header.getOpenBusiType(), header.getOpenLaunchDate() == null ? yyyyMMdd.format(new Date()) : header.getOpenLaunchDate(), header.getOpenLaunchTime() == null ? HHmmss.format(new Date()) : header.getOpenLaunchTime(), header.getOpenMerFlowId(), header.getOpenTransCode(), reqBody});


        logger.debug("è¯·æ±æ¥æææ: " + reqJson);
        String encryptBusiness = null;
        String signData = null;


        signData = signature(reqJson);

        encryptBusiness = encryptBusiness(reqJson);

        JSONObject jsonObject = JSONObject.fromObject("{}");

        jsonObject.put("encryptBody", encryptBusiness);
        jsonObject.put("sign", signData);

        return jsonObject.toString();
    }

    private Boolean verifySign1(byte[] msg, String signData)
            throws Exception {
        PublicKey signpublicKey = getPublicKeyValidateSignature();
        if (signpublicKey == null) {
            logger.error("éªç­¾å¬é¥ä¸ºç©º");
            return Boolean.valueOf(false);
        }
        Signature signature = Signature.getInstance("SHA1WithRSA");
        signature.initVerify(signpublicKey);
        signature.update(msg);
        return Boolean.valueOf(signature.verify(Base64.decode(signData.getBytes("utf8"))));
    }

    protected String verifySignBusiness(String business, String signStr)
            throws AppException {
        JSONObject jsonObject = JSONObject.fromObject(business);
        String signData = null;
        if (("".equals(signStr)) || (signStr == null)) {
            signData = (String) jsonObject.remove("signData");
        } else {
            signData = signStr;
        }
        if (signData == null) {
            logger.error("signData is null");
            throw new AppException("IF01036");
        }
        signData = signData.replace("\r|\n", "");


        StringBuffer sbf = formaturlMap(jsonObject);
        business = sbf.toString().replaceAll("\\}\\{", "\\},\\{").replaceAll("\r|\n", "");

        boolean boo = false;
        String encode = this.configuration.getTransferEncode();
        try {
            boo = verifySign1(business.getBytes(encode), signData).booleanValue();
        } catch (Exception e) {
            logger.error("éªç­¾å¼å¸¸", e);
            throw new AppException("IF01001", e);
        }
        if (boo) {
            return business;
        }
        throw new AppException("IF01001");
    }

    protected static StringBuffer formaturlMap(JSONObject jsonObject) {
        if (jsonObject == null) {
            return null;
        }
        StringBuffer plain = new StringBuffer();

        List<Map.Entry<String, Object>> infolds = new ArrayList(jsonObject.entrySet());


        Collections.sort(infolds, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Map.Entry<String, Object> o11 = (Map.Entry<String, Object>) o1;
                Map.Entry<String, Object> o22 = (Map.Entry<String, Object>) o2;
                return ((String) o11.getKey()).compareTo((String) o22.getKey());
            }
        });
        plain.append("{");
        for (Map.Entry<String, Object> item : infolds) {
            String key = (String) item.getKey();
            if (!"commonDataList".equalsIgnoreCase(key)) {
                plain.append("\"" + key + "\"");
                plain.append(":");
                Object val = item.getValue();
                if ((val instanceof JSONObject)) {
                    StringBuffer str = formaturlMap((JSONObject) val);
                    plain.append(str);
                } else if ((val instanceof String)) {
                    plain.append("\"" + val + "\"");
                } else if ((val instanceof JSONArray)) {
                    plain.append("[");
                    JSONArray arr = (JSONArray) val;
                    for (int i = 0; i < arr.size(); i++) {
                        StringBuffer tempstr = new StringBuffer();
                        Object obj = arr.get(i);
                        if ((obj instanceof JSONObject)) {
                            tempstr = formaturlMap((JSONObject) obj);
                        }
                        plain.append(tempstr);
                    }
                    plain.append("]");
                }
                plain.append(",");
            }
        }
        plain.delete(plain.length() - 1, plain.length());
        if (plain.length() > 0) {
            plain.append("}");
        }
        return plain;
    }

    public String signature(String input)
            throws AppException {
        try {
            String data = input.replace("\r|\n", "");
            JSONObject jsonObject = JSONObject.fromObject(data);

            StringBuffer sbf = formaturlMap(jsonObject);
            String reSbf = sbf.toString().replaceAll("\\}\\{", "\\},\\{");

            byte[] msg = reSbf.getBytes(ENCODING);


            PrivateKey privateKey = getPrivateKeySignature();
            Signature signature = Signature.getInstance("SHA1WithRSA");
            signature.initSign(privateKey);
            signature.update(msg);
            byte[] bytarr = signature.sign();

            String signData = new String(Base64.encode(bytarr), ENCODING);
            return signData.replace("\r|\n", "");
        } catch (Exception e) {
            logger.error("ç­¾åå¤±è´¥: " + e.getStackTrace(), e);
            throw new AppException("IF01002", e);
        }
    }

    public String decryptDataFromBusiness(String encryptBusinessData)
            throws AppException {
        String decryptedBusinessData = null;
        String[] encryptBusinessDataArr = encryptBusinessData.split("@@");
        String encryptKey = encryptBusinessDataArr[1];
        try {
            RSAPrivateKey privateKey = (RSAPrivateKey) getPrivateKeyDecrypt();
            byte[] keyByte = CBRSA.decrypt(privateKey, Base64.decode(encryptKey.getBytes("UTF-8")));
            String encryptKeystr = new String(keyByte, "UTF-8");
            byte[] decryptedBusinessDataBytes = AESUtil.decrypt(Base64.decode(encryptBusinessDataArr[0].getBytes("UTF-8")), encryptKeystr);
            decryptedBusinessData = new String(decryptedBusinessDataBytes, ENCODING);
        } catch (Exception e) {
            logger.error("è§£å¯æ¥æå¼å¸¸", e);
            throw new AppException("IF01017", e);
        }
        return decryptedBusinessData;
    }

    public String remoteCall(RequestHeader header, String reqBody)
            throws AppException {
        checkConfiguration();

        String encode = this.configuration.getTransferEncode();
        String reqJson = null;
        try {
            reqJson = buildReqJson(header, reqBody);
            logger.debug("è¯·æ±æ¥æå¯æ: " + reqJson);
        } catch (Exception e) {
            logger.error("build ReqJson error", e);
            throw new AppException("IF01001", e);
        }
        HttpClient httpClient = null;
        try {
            httpClient = getHttpClient();
        } catch (KeyManagementException e) {
            logger.error("get httpclient error", e);
            throw new AppException("IF01002", e);
        } catch (NoSuchAlgorithmException e) {
            logger.error("get httpclient error", e);
            throw new AppException("IF01003", e);
        }
        String url = this.configuration.getPublicUrl();
        HttpPost httpPost = new HttpPost(url);
        StringEntity se = null;
        try {
            se = new StringEntity(reqJson);
        } catch (UnsupportedEncodingException e) {
            logger.error("unsupport encoding ", e);
            throw new AppException("IF01004", e);
        }
        se.setContentType("text/json");
        se.setContentEncoding(new BasicHeader("Content-Type", "application/json"));
        httpPost.setEntity(se);
        HttpResponse resp = null;
        try {
            resp = httpClient.execute(httpPost);
        } catch (IOException e) {
            logger.error("httpclient execute error ", e);
            throw new AppException("IF01005", e);
        }
        String respJson = null;
        try {
            respJson = EntityUtils.toString(resp.getEntity(), encode);
        } catch (IOException e) {
            logger.error("encoding response error  ", e);
            throw new AppException("IF01006", e);
        }
        logger.debug("ååºæ¥æå¯æ: " + respJson);
        String realRespJson = null;
        if ((realRespJson = validateSignature(respJson)) == null) {
            logger.error("éªç­¾å¤±è´¥!!");
            return null;
        }
        logger.debug("ååºæ¥æææ: " + realRespJson);
        return realRespJson;
    }

    public String validateSignature(String input)
            throws AppException {
        JSONObject jsonObject = JSONObject.fromObject(input);

        String encryptBusinessData = jsonObject.getString("encryptBody");
        if ((encryptBusinessData == null) || ("".equals(encryptBusinessData))) {
            return null;
        }
        String signStr = jsonObject.getString("sign");

        String decryptedBusinessData = null;
        try {
            if (encryptBusinessData.startsWith("{")) {
                decryptedBusinessData = encryptBusinessData;
            } else {
                decryptedBusinessData = decryptDataFromBusiness(encryptBusinessData);
            }
        } catch (AppException e1) {
            e1.printStackTrace();
            return null;
        }
        try {
            decryptedBusinessData = verifySignBusiness(decryptedBusinessData, signStr);
        } catch (AppException e) {
            logger.error("éªè¯ç­¾åå¼å¸¸", e);
            throw e;
        } catch (Exception e) {
            logger.error("éªç­¾å¤±è´¥: ", e);

            throw new AppException("IF01007", e);
        }
        return decryptedBusinessData;
    }

    public String encryptBusiness(String data)
            throws AppException {
        String encryptKey = AESUtil.getRandomAESKey();
        byte[] encryptBusinessDataByte = AESUtil.encrypt(data, encryptKey);
        String encryptBusiness = null;
        try {
            encryptBusiness = new String(Base64.encode(encryptBusinessDataByte), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException();
        }
        PublicKey publicKey = getPublicKeyEncrypt();
        if (publicKey == null) {
            encryptBusiness = data;
        } else {
            byte[] encryptKeyByte = new byte[0];
            try {
                encryptKeyByte = CBRSA.encrypt((RSAKey) publicKey, encryptKey.getBytes("UTF-8"));
            } catch (CBException e) {
                logger.error("CBException", e);
                throw new AppException("IF01008", e);
            } catch (UnsupportedEncodingException e) {
                logger.error("UnsupportedEncodingException", e);

                throw new IllegalArgumentException();
            }
            String encryptKeyString = null;
            try {
                encryptKeyString = new String(Base64.encode(encryptKeyByte), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException();
            }
            encryptBusiness = encryptBusiness + "@@" + encryptKeyString;
        }
        return encryptBusiness;
    }
}
